﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Clustering
{
    /// <summary>
    /// This enum holds all the distances supported in out project
    /// </summary>
    public enum DistanceMeasurement
    {
        EuclidianDistance,
        ManhattanDistance,
        //MaxDistance,
        //KS_LikeDistance,
        //CorreleationDissimilarity,
        PearsonDissimilarity,
        SpearmanDissimilarity,
        //KendallRandDIssimilarity
    }

    class Distance
    {
        private static double EclidianDistance(double[] X, double[] Y)
        {
            return 0;
        }
        
        /// <summary>
        /// This is the constractor
        /// </summary>
        /// <param name="X">vector of doubles</param>
        /// <param name="Y">vectore of doubles</param>
        /// <param name="func">name of distanse function</param>
        /// <returns>the distance between the vectors</returns>
        private DistanceMeasurement distanceMethod = DistanceMeasurement.EuclidianDistance;  //default distance euclidian

        private delegate double distanceDelegate(double[] A, double[] B);               // reference to function type
        private distanceDelegate distance = new distanceDelegate(euclideanDistance);   //default refrence to euclidain
        private distanceDelegate correlation = new distanceDelegate(pearsonCorrelation); //default correlation
        /// <summary>
        /// set or get distance calculation method
        /// </summary>

        public DistanceMeasurement DistanceMethod
        {
            get { return distanceMethod; }
            set 
            { 
                distanceMethod = value;
                switch (distanceMethod)
                {
                    case DistanceMeasurement.EuclidianDistance: distance = new distanceDelegate(euclideanDistance); 
                                                                break;
                    case DistanceMeasurement.ManhattanDistance: distance = new distanceDelegate(manhattanDistance); 
                                                                break;
                    case DistanceMeasurement.PearsonDissimilarity:
                                                                distance = new distanceDelegate(correlationDistance);
                                                                    correlation = new distanceDelegate(pearsonCorrelation);
                                                                    break;
                    case DistanceMeasurement.SpearmanDissimilarity:
                                                                    distance = new distanceDelegate(correlationDistance);
                                                                    correlation = new distanceDelegate(spearmanCorrelation);
                                                                    break;
                  /*  case DistanceMeasurement.KendallRandDIssimilarity:
                                                                    distance = new distanceDelegate(correlationDistance);
                                                                    correlation = new distanceDelegate(kendallCorrelation);
                                                                    break;  */
                }
            }
        }      


        public double calculatDistance(double[] X, double[] Y)
        {
            return distance(X,Y);
        }


        private double manhattanDistance(double[] X, double[] Y)
        {
            int count = 0;

            double distance = 0.0;

            double sum = 0.0;

            if (X.GetUpperBound(0) != Y.GetUpperBound(0))
            {
                throw new System.ArgumentException("the number of elements in X must match the number of elements in Y");
            }
            else
            {
                count = X.Length;
            }

            for (int i = 0; i < count; i++)
            {
                sum = sum + Math.Abs(X[i] - Y[i]);
            }

            distance = sum;

            return distance;
          
        }

        private double correlationDistance(double[] X, double[] Y)
        {
            return (1 - correlation(X, Y)) / 2;
        }

        private static double pearsonCorrelation(double[] X, double[] Y)
        {
            double distance = 0.0;
            double[] A = new double[X.Length];
            double[] XX = new double[X.Length];
            double[] YY = new double[X.Length];
            double avgX;


            avgX = X.Average();

            for (int i = 0; i < X.Length; i++)
            {
                A[i] = X[i] * Y[i];
                XX[i] = X[i] * X[i];
                YY[i] = Y[i] * Y[i];
            }
            distance = (A.Average() - (X.Average() * Y.Average())) / (Math.Sqrt(XX.Average() - (X.Average() * X.Average())) * Math.Sqrt(YY.Average() - (Y.Average() * Y.Average())));
            
            return distance;
        }
        private static double spearmanCorrelation(double[] X, double[] Y)
        {
            int n;                  // size of vectors
            double distance=0.0;
            double sum = 0.0;

            n = X.Length;
            if ((n==1)||(n==0)) return 0;
            for (int i = 0; i < n; i++)         // calculat sum
            {
                sum+=((X[i]-Y[i])*(X[i]-Y[i]));
            }
            distance=1-(6*sum)/(n*(n*n-1));     // calculate spearman rank correlation
                        
            return distance;
        }
        /*private static double kendallCorrelation(double[] X, double[] Y)
        {
            int n;                  // size of vectors
            int nc=0;                 // number of concordant pairs
            int nd=0;                 // number of discordant pairs
            double distance = 0.0;

            n = X.Length;
            if ((n==1)||(n==0)) return 0;
            distance = (nc - nd) / (0.5 * n * (n - 1));

            return distance;
        }  */
 
        /// <summary>
        /// Calculates the Euclidean Distance Measure between two data points
        /// </summary>
        /// <param name="X">An array with the values of an object or datapoint</param>
        /// <param name="Y">An array with the values of an object or datapoint</param>
        /// <returns>Returns the Euclidean Distance Measure Between Points X and Points Y</returns>
        private static double euclideanDistance(double[] X, double[] Y)
        {
            int count = 0;

            double distance = 0.0;

            double sum = 0.0;

            if (X.GetUpperBound(0) != Y.GetUpperBound(0))
            {
                throw new System.ArgumentException("the number of elements in X must match the number of elements in Y");
            }
            else
            {
                count = X.Length;
            }

            for (int i = 0; i < count; i++)
            {
                sum = sum + (X[i] - Y[i]) * (X[i] - Y[i]);
            }

            distance = Math.Sqrt(sum);

            return distance;
        }

    }
}
